/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkMethod, LineColPosition, ArkClass } from "arkanalyzer";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { MethodMatcher, MatcherCallback, MatcherTypes } from '../../Index';
import { Defects, IssueReport } from "../../model/Defects";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { CONSTRUCTOR_NAME } from 'arkanalyzer/lib/core/common/TSConst';
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';

const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'ArrayTypeCheck');

const gMetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/default-param-last.md",
    description: "Default parameters should be last.",
};

export class DefaultParamLastCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gMetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];

    private buildMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD,
    };

    public registerMatchers(): MatcherCallback[] {
        const matchBuildCb: MatcherCallback = {
            matcher: this.buildMatcher,
            callback: this.check
        };
        return [matchBuildCb];
    };

    public check = (method: ArkMethod) => {
        let hasOptional = false;
        let lastOptionalPos: [number, number] | null = null;

        for (let param of method.getParameters()) {
            if (param.isOptional()) {
                hasOptional = true;
                lastOptionalPos = this.getParamPosition(param.getName(), method, method.getDeclaringArkClass());
            } else if (hasOptional) {
                if (lastOptionalPos) {
                    this.addIssueReportNode(...lastOptionalPos, method.getDeclaringArkFile().getFilePath());
                } else {
                    logger.error(`[DefaultParamLastCheck] Failed to get position for optional parameter in method '${method.getName()}'.`);
                }
                break;
            }
        }
    };

    private getParamPosition(paramName: string, method: ArkMethod, arkClass: ArkClass): [number, number] | null {
        const pos = method.getBody()?.getLocals().get(paramName)
        ?.getDeclaringStmt()?.getOperandOriginalPositions()?.[1];
        if (!pos) {
            return null;
        }

        if (method.getName() === CONSTRUCTOR_NAME) {
            const fieldPos = arkClass.getFieldWithName(paramName)?.getOriginPosition();
            if (
                fieldPos instanceof LineColPosition &&
                fieldPos.getLineNo() === pos.getFirstLine() &&
                fieldPos.getColNo() !== pos.getFirstCol()
            ) {
                return [pos.getFirstLine(), fieldPos.getColNo()];
            }
        }
        return [pos.getFirstLine(), pos.getFirstCol()];
    }

    private addIssueReportNode(line: number, startCol: number, filePath: string): void {
        const severity = this.rule.alert ?? this.metaData.severity;
        let defect = new Defects(line, startCol, startCol, this.metaData.description, severity,
            this.rule.ruleId, filePath, this.metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defect, undefined));
        RuleListUtil.push(defect);
    }
}
